#include "lcd154.hpp"

namespace LCD
{
    std::ostream &operator<<(std::ostream &os, Point point) noexcept
    {
        return os << point.to_string();
    }
    std::string Point::to_string() const noexcept
    {
        return std::format("({}, {})", x, y);
    }
}

namespace LCD
{

    TFTLCD::TFTLCD(SPI_HandleTypeDef &hspi)
        : hspi(hspi),
          width(ScreenWidth),
          height(ScreenHeight),
          color(LCD::WHITE),
          backColor(LCD::BLACK),
          direction(DisplayDirection::Horizontal),
          cursor(Point::Origin())
    {
    }

    void TFTLCD::setBackLightPin(GPIO_TypeDef *port, uint16_t pin)
    {
        backLightPort = port;
        backLightPin  = pin;
    }

    void TFTLCD::setCSPin(GPIO_TypeDef *port, uint16_t pin)
    {
        csPort = port;
        csPin  = pin;
    }

    void TFTLCD::setDCPin(GPIO_TypeDef *port, uint16_t pin)
    {
        dcPort = port;
        dcPin  = pin;
    }

    void TFTLCD::init(void)
    {
        // 初始化SPI和GPIO（应在构造函数中完成）

        HAL_Delay(10); // 等待复位稳定

        // 显存访问控制
        writeCommand(0x36);
        writeData(0x00_u8); // 默认方向：从左到右，从上到下

        // 接口像素格式
        writeCommand(0x3A);
        writeData(0x05_u8); // 16位色（RGB565）

        // 电压相关配置
        writeCommand(0xB2);
        writeData(0x0C_u8);
        writeData(0x0C_u8);
        writeData(0x00_u8);
        writeData(0x33_u8);
        writeData(0x33_u8);

        writeCommand(0xB7); // 栅极电压
        writeData(0x35_u8);

        writeCommand(0xBB); // 公共电压
        writeData(0x19_u8);

        writeCommand(0xC0);
        writeData(0x2C_u8);

        writeCommand(0xC2); // VDV/VRH配置
        writeData(0x01_u8);

        writeCommand(0xC3); // VRH电压
        writeData(0x12_u8);

        writeCommand(0xC4); // VDV电压
        writeData(0x20_u8);

        writeCommand(0xC6); // 帧率控制
        writeData(0x0F_u8); // 60Hz

        writeCommand(0xD0); // 电源控制
        writeData(0xA4_u8);
        writeData(0xA1_u8);

        // 伽马校正
        writeCommand(0xE0); // 正极伽马
        constexpr std::array<uint8_t, 14> gammaP = {
            0xD0, 0x04, 0x0D, 0x11, 0x13, 0x2B, 0x3F,
            0x54, 0x4C, 0x18, 0x0D, 0x0B, 0x1F, 0x23};
        for (auto val : gammaP) writeData(val);

        writeCommand(0xE1); // 负极伽马
        constexpr std::array<uint8_t, 14> gammaN = {
            0xD0, 0x04, 0x0C, 0x11, 0x13, 0x2C, 0x3F,
            0x44, 0x51, 0x2F, 0x1F, 0x1F, 0x20, 0x23};
        for (auto val : gammaN) writeData(val);

        writeCommand(0x21); // 反显模式（常黑型面板）

        // 退出休眠
        writeCommand(0x11);
        HAL_Delay(120); // 等待电源稳定

        // 开启显示
        writeCommand(0x29);

        // 初始清屏
        setDisplayDirection(this->direction);
        clear();

        backLightOn();
    }

    void TFTLCD::setDisplayDirection(DisplayDirection direction)
    {
        this->direction = direction;

        writeCommand(0x36); // 显存访问控制 指令，用于设置访问显存的方式

        switch (direction) {
            case DisplayDirection::Horizontal:
                writeData(0x70_u8); // 横屏显示
                this->cursor = Point(0, 0);
                this->width  = ScreenHeight;
                this->height = ScreenWidth;
                break;
            case DisplayDirection::HorizontalFlip:
                writeData(0xA0_u8); // 横屏显示 ，并左右翻转，RGB像素格式
                this->cursor = Point(80, 0);
                this->width  = ScreenHeight;
                this->height = ScreenWidth;
                break;
            case DisplayDirection::Vertical:
                writeData(0x00_u8); // 垂直显示
                this->cursor = Point(0, 0);
                this->width  = ScreenWidth;
                this->height = ScreenHeight;
                break;
            case DisplayDirection::VerticalFlip:
                writeData(0xC0_u8); // 垂直显示 ，并上下翻转，RGB像素格式
                this->cursor = Point(0, 80);
                this->width  = ScreenWidth;
                this->height = ScreenHeight;
                break;
            default:
                break;
        }
    }

    void TFTLCD::setDrawArea(Point topLeft, Point bottomRight)
    {
        writeCommand(0x2A); //	列地址设置，即X坐标
        writeData(static_cast<std::uint16_t>(topLeft.x + this->cursor.x));
        writeData(static_cast<std::uint16_t>(bottomRight.x + this->cursor.x));

        writeCommand(0x2B); //	行地址设置，即Y坐标
        writeData(static_cast<std::uint16_t>(topLeft.y + this->cursor.y));
        writeData(static_cast<std::uint16_t>(bottomRight.y + this->cursor.y));

        writeCommand(0x2C); //	开始写入显存，即要显示的颜色数据
    }

    void TFTLCD::setColor(RGB565 color)
    {
        this->color = color;
    }

    void TFTLCD::setBackColor(RGB565 color)
    {
        this->backColor = color;
    }

    void TFTLCD::clear(Point topLeft, Point bottomRight)
    {
        auto currentColor = this->color;
        setColor(this->backColor);
        fillRectangle(topLeft, bottomRight);
        setColor(currentColor);
    }

    void TFTLCD::clear(void)
    {
        clear(Point::Origin(), Point(this->width - 1, this->height - 1));
    }

    void TFTLCD::drawPoint(Point point)
    {
        if (not point.within(Point::Origin(), Point(this->width - 1, this->height - 1)))
            return;
        setDrawArea(point, point);
        writeData(this->color.value());
    }

    void TFTLCD::drawLine(Point start, Point end)
    {
        auto SCREEN_TOP_LEFT     = Point::Origin();
        auto SCREEN_BOTTOM_RIGHT = Point(this->width - 1, this->height - 1);

        // 如果是水平或垂直直线，调用 fillRectangle 绘制
        if (start.x == end.x or start.y == end.y) {
            fillRectangle(start, end);
            return;
        }

        // 完全在屏幕外的线段快速跳过
        if ((start.x < 0 && end.x < 0) ||
            (start.y < 0 && end.y < 0) ||
            (start.x > 239 && end.x > 239) ||
            (start.y > 239 && end.y > 239)) {
            return;
        }

        // 使用Bresenham算法绘制直线
        int16_t dx  = std::abs(end.x - start.x);
        int16_t dy  = -std::abs(end.y - start.y);
        int16_t sx  = (start.x < end.x) ? 1 : -1;
        int16_t sy  = (start.y < end.y) ? 1 : -1;
        int16_t err = dx + dy;
        int16_t err2;

        while (true) {
            if (start.within(SCREEN_TOP_LEFT, SCREEN_BOTTOM_RIGHT)) {
                drawPoint(start);
            }

            // 到达终点
            if (start.x == end.x && start.y == end.y) break;

            err2 = 2 * err;

            // 调整误差和坐标
            if (err2 >= dy) {
                err += dy;
                start.x += sx;
            }
            if (err2 <= dx) {
                err += dx;
                start.y += sy;
            }
        }
    }

    void TFTLCD::drawCircle(Point center, std::uint16_t radius)
    {
        if (radius == 0) {
            drawPoint(center); // 半径为 0，直接画点
            return;
        }

        std::int16_t x = 0;
        std::int16_t y = radius;
        std::int16_t d = 1 - radius; // 初始决策参数

        // 绘制圆的 8 个对称点
        auto drawCirclePoints = [this, &center](std::int16_t x, std::int16_t y) {
            drawPoint({center.x + x, center.y + y});
            drawPoint({center.x - x, center.y + y});
            drawPoint({center.x + x, center.y - y});
            drawPoint({center.x - x, center.y - y});
            drawPoint({center.x + y, center.y + x});
            drawPoint({center.x - y, center.y + x});
            drawPoint({center.x + y, center.y - x});
            drawPoint({center.x - y, center.y - x});
        };

        while (x <= y) {
            drawCirclePoints(x, y);

            if (d < 0) {
                d += 2 * x + 3; // 选择 E（East）点
            } else {
                d += 2 * (x - y) + 5; // 选择 SE（South-East）点
                y--;
            }
            x++;
        }
    }

    void TFTLCD::drawRectangle(Point topLeft, Point bottomRight)
    {
        int16_t x1 = topLeft.x;
        int16_t y1 = topLeft.y;
        int16_t x2 = bottomRight.x;
        int16_t y2 = bottomRight.y;

        // 绘制四条边
        drawLine({x1, y1}, {x2, y1}); // 上边
        drawLine({x2, y1}, {x2, y2}); // 右边
        drawLine({x2, y2}, {x1, y2}); // 下边
        drawLine({x1, y2}, {x1, y1}); // 左边
    }

    void TFTLCD::fillRectangle(Point topLeft, Point bottomRight)
    {
        // 如果 bottomRight 在 topLeft 左上，则调转顺序
        if (bottomRight.x < topLeft.x or bottomRight.y < topLeft.y) {
            std::swap(topLeft, bottomRight);
        }

        // 如果某点在屏幕外，则就近到屏幕边缘（<0->0，>239->239）
        topLeft.x     = std::clamp<decltype(topLeft.x)>(topLeft.x, 0, 239);
        topLeft.y     = std::clamp<decltype(topLeft.y)>(topLeft.y, 0, 239);
        bottomRight.x = std::clamp<decltype(bottomRight.x)>(bottomRight.x, 0, 239);
        bottomRight.y = std::clamp<decltype(bottomRight.y)>(bottomRight.y, 0, 239);

        std::uint16_t width  = (bottomRight - topLeft).x + 1;
        std::uint16_t height = (bottomRight - topLeft).y + 1;

        setDrawArea(topLeft, bottomRight);

        std::uint32_t totalPixels = width * height;
        std::uint32_t bufferSize  = this->BufferSize;

        // 填充缓冲区
        std::fill_n(
            this->buffer.begin(),
            std::min(totalPixels, bufferSize),
            this->color.value());

        // 分块写入
        std::uint32_t remaining = totalPixels;
        while (remaining > 0) {
            auto chunkSize = std::min(remaining, bufferSize);

            writeBuffer(chunkSize);
            remaining -= chunkSize;
        }
    }

    void TFTLCD::fillCircle(Point center, std::uint16_t radius)
    {
        if (radius == 0) {
            drawPoint(center); // 半径为 0，直接画点
            return;
        }

        int x = 0;
        int y = radius;
        int d = 1 - radius; // 初始决策参数

        // 填充圆的水平线
        auto fillHorizontalLines = [this, &center](int x, int y) {
            // 绘制从 (center.x - x, center.y + y) 到 (center.x + x, center.y + y)
            drawLine({center.x - x, center.y + y}, {center.x + x, center.y + y});
            // 绘制从 (center.x - x, center.y - y) 到 (center.x + x, center.y - y)
            drawLine({center.x - x, center.y - y}, {center.x + x, center.y - y});
            // 绘制从 (center.x - y, center.y + x) 到 (center.x + y, center.y + x)
            drawLine({center.x - y, center.y + x}, {center.x + y, center.y + x});
            // 绘制从 (center.x - y, center.y - x) 到 (center.x + y, center.y - x)
            drawLine({center.x - y, center.y - x}, {center.x + y, center.y - x});
        };

        while (x <= y) {
            fillHorizontalLines(x, y); // 填充水平线

            if (d < 0) {
                d += 2 * x + 3; // 选择 E（East）点
            } else {
                d += 2 * (x - y) + 5; // 选择 SE（South-East）点
                y--;
            }
            x++;
        }
    }

    void TFTLCD::backLightOn(void)
    {
        HAL_GPIO_WritePin(backLightPort, backLightPin, GPIO_PIN_SET);
    }

    void TFTLCD::backLightOff(void)
    {
        HAL_GPIO_WritePin(backLightPort, backLightPin, GPIO_PIN_RESET);
    }

    void TFTLCD::SPI_SetDataSize(std::uint32_t dataSize)
    {
        __HAL_SPI_DISABLE(&this->hspi);

        if (dataSize == SPI_DATASIZE_8BIT) {
            this->hspi.Instance->CR1 &= 0xF7FF;
        } else if (dataSize == SPI_DATASIZE_16BIT) {
            this->hspi.Instance->CR1 |= 0x0800;
        }

        __HAL_SPI_ENABLE(&this->hspi);
    }

    void TFTLCD::chipSelect(void)
    {
        HAL_GPIO_WritePin(csPort, csPin, GPIO_PIN_RESET);
    }

    void TFTLCD::chipDeselect(void)
    {
        HAL_GPIO_WritePin(csPort, csPin, GPIO_PIN_SET);
    }

    void TFTLCD::setCommandMode(void)
    {
        HAL_GPIO_WritePin(this->dcPort, this->dcPin, GPIO_PIN_RESET);
    }

    void TFTLCD::setDataMode(void)
    {
        HAL_GPIO_WritePin(this->dcPort, this->dcPin, GPIO_PIN_SET);
    }

    void TFTLCD::writeCommand(std::uint8_t command)
    {
        setCommandMode();
        writeData(command);
        setDataMode();
    }

    void TFTLCD::writeData(std::uint8_t data)
    {
        writeData(&data, 1);
    }

    void TFTLCD::writeData(const std::uint8_t *pData, std::uint16_t size)
    {
        chipSelect();
        HAL_SPI_Transmit(&this->hspi, pData, size, 1000);
        chipDeselect();
    }

    void TFTLCD::writeData(std::uint16_t data)
    {
        std::uint8_t buffer[2];
        buffer[0] = (data >> 8) & 0xFF;
        buffer[1] = data & 0xFF;
        writeData(buffer, 2);
    }

    void TFTLCD::writeData(const std::uint16_t *pData, std::uint16_t size)
    {
        SPI_SetDataSize(SPI_DATASIZE_16BIT);
        chipSelect();

        for (std::uint32_t i = 0; i < size; i++) {
            this->hspi.Instance->DR = *pData;
            pData++;
            while ((this->hspi.Instance->SR & 0x0002) == 0);
        }
        while ((this->hspi.Instance->SR & 0x0080) != RESET);

        chipDeselect();

        SPI_SetDataSize(SPI_DATASIZE_8BIT);
    }

    void TFTLCD::writeBuffer(void)
    {
        writeBuffer(this->BufferSize);
    }

    void TFTLCD::writeBuffer(std::uint16_t size)
    {
        chipSelect();

        writeData(const_cast<std::uint16_t *>(this->buffer.data()), size);

        chipDeselect();
    }
}